﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel.Syndication;
using System.Xml;
using System.Xml.Linq;
using NCMIS.ObjectModel;
using NCMIS.ObjectModel.MetaData;
using NCMIS.ObjectModel.Rest;
using System.IO;

namespace NCMIS.ServiceModel.Syndication
{
    /// <summary>
    /// Represents a CMIS feed item, extended with Atom Extensions for CMIS.
    /// </summary>
    [Serializable]
    public class CmisSyndicationItem : SyndicationItem
    {
        #region SyndicationLinks

        // The SyndicationLinks will have defaulted to null. If user enters a URI they will also be assigned their
        // appropriate RelationshipType and MediaType. Only link != null should be serialized to XML.
        private SyndicationLink _alternateLink;
        private SyndicationLink _currentVersionLink;
        private SyndicationLink _describedByLink;
        private SyndicationLink _downLink;
        private SyndicationLink _editLink;
        private SyndicationLink _editMediaLink;
        private SyndicationLink _enclosureLink;
        private SyndicationLink _selfLink;
        private SyndicationLink _serviceLink;
        private SyndicationLink _upLink;
        private SyndicationLink _versionHistoryLink;
        private SyndicationLink _workingCopyLink;

        private SyndicationLink _aclLink;
        private SyndicationLink _allowableActionsLink;
        private SyndicationLink _folderTreeLink;
        private SyndicationLink _policiesLink;
        private SyndicationLink _relationshipsLink;
        private SyndicationLink _sourceLink;
        private SyndicationLink _targetLink;

        #region Setters

        /// <summary>
        /// Sets the Alternate Link URI, and assigns its RelationshipType and MediaType.
        /// </summary>
        public Uri AlternateLinkUri
        {
            set
            {
                if (value == null)
                {
                    _alternateLink = null;
                }
                else
                {
                    _alternateLink = new SyndicationLink()
                    {
                        Uri = value,
                        RelationshipType = CmisLinkRel.Alternate,
                        MediaType = CmisContentType.Entry, // TODO: Should be media type of the Rendition
                    };
                }
            }
        }

        /// <summary>
        /// Sets the Current-Version Link URI, and assigns its RelationshipType and MediaType.
        /// </summary>
        public Uri CurrentVersionLinkUri
        {
            set
            {
                if (value == null)
                {
                    _currentVersionLink = null;
                }
                else
                {
                    _currentVersionLink = new SyndicationLink()
                    {
                        Uri = value,
                        RelationshipType = CmisLinkRel.CurrentVersion,
                        MediaType = CmisContentType.Feed,
                    };
                }
            }
        } 

        /// <summary>
        /// Sets the DescribedBy Link URI, and assigns its RelationshipType and MediaType.
        /// </summary>
        public Uri DescribedByLinkUri
        {
            set
            {
                if (value == null)
                {
                    _describedByLink = null;
                }
                else
                {
                    _describedByLink = new SyndicationLink()
                    {
                        Uri = value,
                        RelationshipType = CmisLinkRel.DescribedBy,
                        MediaType = CmisContentType.Entry,
                    };
                }
            }
        }

        /// <summary>
        /// Sets the Down Link URI, and assigns its RelationshipType and MediaType.
        /// </summary>
        public Uri DownLinkUri
        {
            set
            {
                if (value == null)
                {
                    _downLink = null;
                }
                else
                {
                    _downLink = new SyndicationLink()
                    {
                        Uri = value,
                        RelationshipType = CmisLinkRel.Down,
                        MediaType = CmisContentType.Feed,
                    };
                }
            }
        }

        /// <summary>
        /// Sets the Edit Link URI, and assigns its RelationshipType and MediaType. Applicable to CMIS Document. Must match the UriTemplates of services
        /// getObject (GET), updateProperties (PUT).
        /// </summary>
        public Uri EditLinkUri
        {
            set
            {
                if (value == null)
                {
                    _editLink = null;
                }
                else
                {
                    _editLink = new SyndicationLink()
                    {
                        Uri = value,
                        RelationshipType = CmisLinkRel.Edit,
                        MediaType = CmisContentType.Entry,
                    };
                }
            }
        }

        /// <summary>
        /// Sets the Edit-Media Link URI, and assigns its RelationshipType and MediaType. Applicable to CMIS Document. Must match the UriTemplates of services
        /// setContentStream (PUT) , deleteContentStream (DELETE).
        /// </summary>
        public Uri EditMediaLinkUri
        {
            set
            {
                if (value == null)
                {
                    _editMediaLink = null;
                }
                else
                {
                    _editMediaLink = new SyndicationLink()
                    {
                        Uri = value,
                        RelationshipType = CmisLinkRel.EditMedia,
                        MediaType = CmisContentType.Stream,
                    };
                }
            }
        }

        /// <summary>
        /// Sets the Enclosure Link URI, and assigns its RelationshipType and MediaType.
        /// </summary>
        public Uri EnclosureLinkUri
        {
            set
            {
                if (value == null)
                {
                    _enclosureLink = null;
                }
                else
                {
                    _enclosureLink = new SyndicationLink()
                    {
                        Uri = value,
                        RelationshipType = CmisLinkRel.Enclosure,
                        MediaType = CmisContentType.Stream,
                    };
                }
            }
        }

        /// <summary>
        /// Sets the Self Link URI, and assigns its RelationshipType and MediaType.
        /// </summary>
        public Uri SelfLinkUri
        {
            set
            {
                if (value == null)
                {
                    _selfLink = null;
                }
                else
                {
                    _selfLink = new SyndicationLink()
                    {
                        Uri = value,
                        RelationshipType = CmisLinkRel.Self,
                        MediaType = CmisContentType.Entry,
                    };
                }
            }
        }

        /// <summary>
        /// Sets the Service Link URI, and assigns its RelationshipType and MediaType.
        /// </summary>
        public Uri ServiceLinkUri
        {
            set
            {
                if (value == null)
                {
                    _serviceLink = null;
                }
                else
                {
                    _serviceLink = new SyndicationLink()
                    {
                        Uri = value,
                        RelationshipType = CmisLinkRel.Service,
                        MediaType = CmisContentType.Service,
                    };
                }
            }
        }

        /// <summary>
        /// Sets the Up Link URI, and assigns its RelationshipType and MediaType.
        /// </summary>
        public Uri UpLinkUri
        {
            set
            {
                if (value == null)
                {
                    _upLink = null;
                }
                else
                {
                    _upLink = new SyndicationLink()
                    {
                        Uri = value,
                        RelationshipType = CmisLinkRel.Up,
                        MediaType = CmisContentType.Feed,
                    };
                }
            }
        }

        /// <summary>
        /// Sets the Version-History Link URI, and assigns its RelationshipType and MediaType.
        /// </summary>
        public Uri VersionHistoryLinkUri
        {
            set
            {
                if (value == null)
                {
                    _versionHistoryLink = null;
                }
                else
                {
                    _versionHistoryLink = new SyndicationLink()
                    {
                        Uri = value,
                        RelationshipType = CmisLinkRel.VersionHistory,
                        MediaType = CmisContentType.Feed,
                    };
                }
            }
        }

        /// <summary>
        /// Sets the Working-Copy Link URI, and assigns its RelationshipType and MediaType.
        /// </summary>
        public Uri WorkingCopyLinkUri
        {
            set
            {
                if (value == null)
                {
                    _workingCopyLink = null;
                }
                else
                {
                    _workingCopyLink = new SyndicationLink()
                    {
                        Uri = value,
                        RelationshipType = CmisLinkRel.WorkingCopy,
                        MediaType = CmisContentType.Entry,
                    };
                }
            }
        }

        /// <summary>
        /// Sets the ACL Link URI, and assigns its RelationshipType and MediaType.
        /// </summary>
        public Uri AclLinkUri
        {
            set
            {
                if (value == null)
                {
                    _aclLink = null;
                }
                else
                {
                    _aclLink = new SyndicationLink()
                    {
                        Uri = value,
                        RelationshipType = CmisLinkRel.Acl,
                        MediaType = CmisContentType.Acl,
                    };
                }
            }
        }

        /// <summary>
        /// Sets the AllowableActions Link URI, and assigns its RelationshipType and MediaType.
        /// </summary>
        public Uri AllowableActionsLinkUri
        {
            set
            {
                if (value == null)
                {
                    _allowableActionsLink = null;
                }
                else
                {
                    _allowableActionsLink = new SyndicationLink()
                    {
                        Uri = value,
                        RelationshipType = CmisLinkRel.AllowableActions,
                        MediaType = CmisContentType.AllowableActions,
                    };
                }
            }
        }

        /// <summary>
        /// Sets the FolderTree Link URI, and assigns its RelationshipType and MediaType.
        /// </summary>
        public Uri FolderTreeLinkUri
        {
            set
            {
                if (value == null)
                {
                    _folderTreeLink = null;
                }
                else
                {
                    _folderTreeLink = new SyndicationLink()
                    {
                        Uri = value,
                        RelationshipType = CmisLinkRel.FolderTree,
                        MediaType = CmisContentType.Feed,
                    };
                }
            }
        }

        /// <summary>
        /// Sets the Policies Link URI, and assigns its RelationshipType and MediaType.
        /// </summary>
        public Uri PoliciesLinkUri
        {
            set
            {
                if (value == null)
                {
                    _policiesLink = null;
                }
                else
                {
                    _policiesLink = new SyndicationLink()
                    {
                        Uri = value,
                        RelationshipType = CmisLinkRel.Policies,
                        MediaType = CmisContentType.Feed,
                    };
                }
            }
        }

        /// <summary>
        /// Sets the Relationships Link URI, and assigns its RelationshipType and MediaType.
        /// </summary>
        public Uri RelationshipsLinkUri
        {
            set
            {
                if (value == null)
                {
                    _relationshipsLink = null;
                }
                else
                {
                    _relationshipsLink = new SyndicationLink()
                    {
                        Uri = value,
                        RelationshipType = CmisLinkRel.Relationships,
                        MediaType = CmisContentType.Feed,
                    };
                }
            }
        }

        /// <summary>
        /// Sets the Source Link URI, and assigns its RelationshipType and MediaType.
        /// </summary>
        public Uri SourceLinkUri
        {
            set
            {
                if (value == null)
                {
                    _sourceLink = null;
                }
                else
                {
                    _sourceLink = new SyndicationLink()
                    {
                        Uri = value,
                        RelationshipType = CmisLinkRel.Source,
                        MediaType = CmisContentType.Entry,
                    };
                }
            }
        }

        /// <summary>
        /// Sets the Target Link URI, and assigns its RelationshipType and MediaType.
        /// </summary>
        public Uri TargetLinkUri
        {
            set
            {
                if (value == null)
                {
                    _targetLink = null;
                }
                else
                {
                    _targetLink = new SyndicationLink()
                    {
                        Uri = value,
                        RelationshipType = CmisLinkRel.Target,
                        MediaType = CmisContentType.Entry,
                    };
                }
            }
        }

        #endregion

        #region Getters

        /// <summary>
        /// Gets the Alternate link.
        /// </summary>
        public SyndicationLink AlternateLink
        {
            get
            {
                return _alternateLink;
            }
        }

        /// <summary>
        /// Gets the Current-Version link.
        /// </summary>
        public SyndicationLink CurrentVersionLink
        {
            get
            {
                return _currentVersionLink;
            }
        }

        /// <summary>
        /// Gets the DescribedBy link.
        /// </summary>
        public SyndicationLink DescribedByLink
        {
            get
            {
                return _describedByLink;
            }
        }

        /// <summary>
        /// Gets the Down link.
        /// </summary>
        public SyndicationLink DownLink
        {
            get
            {
                return _downLink;
            }
        }

        /// <summary>
        /// Gets the Edit link.
        /// </summary>
        public SyndicationLink EditLink
        {
            get
            {
                return _editLink;
            }
        }

        /// <summary>
        /// Gets the Edit-Media link.
        /// </summary>
        public SyndicationLink EditMediaLink
        {
            get
            {
                return _editMediaLink;
            }
        }

        /// <summary>
        /// Gets the Self Link.
        /// </summary>
        public SyndicationLink SelfLink
        {
            get
            {
                return _selfLink;
            }
        }

        /// <summary>
        /// Gets the Service Link, pointing to the service document.
        /// </summary>
        public SyndicationLink ServiceLink
        {
            get
            {
                return _serviceLink;
            }
        }

        /// <summary>
        /// Gets the Up Link.
        /// </summary>
        public SyndicationLink UpLink
        {
            get
            {
                return _upLink;
            }
        }

        /// <summary>
        /// Gets the Version-History Link.
        /// </summary>
        public SyndicationLink VersionHistoryLink
        {
            get
            {
                return _versionHistoryLink;
            }
        }

        /// <summary>
        /// Gets the Working-Copy Link.
        /// </summary>
        public SyndicationLink WorkingCopyLink
        {
            get
            {
                return _workingCopyLink;
            }
        }

        /// <summary>
        /// Gets the ACL link.
        /// </summary>
        public SyndicationLink AclLink
        {
            get
            {
                return _aclLink;
            }
        }

        /// <summary>
        /// Gets the AllowableActions link.
        /// </summary>
        public SyndicationLink AllowableActionsLink
        {
            get
            {
                return _allowableActionsLink;
            }
        }

        /// <summary>
        /// Gets the FolderTree link.
        /// </summary>
        public SyndicationLink FolderTreeLink
        {
            get
            {
                return _folderTreeLink;
            }
        }

        /// <summary>
        /// Gets the Policies link.
        /// </summary>
        public SyndicationLink Policies
        {
            get
            {
                return _policiesLink;
            }
        }

        /// <summary>
        /// Gets the Relationships link.
        /// </summary>
        public SyndicationLink RelationshipsLink
        {
            get
            {
                return _relationshipsLink;
            }
        }

        /// <summary>
        /// Gets the Source link.
        /// </summary>
        public SyndicationLink SourceLink
        {
            get
            {
                return _sourceLink;
            }
        }

        /// <summary>
        /// Gets the Target link.
        /// </summary>
        public SyndicationLink TargetLink
        {
            get
            {
                return _targetLink;
            }
        }

        #endregion

        #endregion

        #region Constrcutors

        /// <summary>
        ///Creates a new instance of <see cref="NCMIS.ServiceModel.Syndication.CmisSyndicationItem"/>. Adds the required XML Namespaces to the instance.
        /// </summary>
        public CmisSyndicationItem()
        {
            this.AttributeExtensions.Add(new XmlQualifiedName("cmis", "http://www.w3.org/2000/xmlns/"), CmisNs.Cmis);
            this.AttributeExtensions.Add(new XmlQualifiedName("cmism", "http://www.w3.org/2000/xmlns/"), CmisNs.Cmism);
            this.AttributeExtensions.Add(new XmlQualifiedName("atom", "http://www.w3.org/2000/xmlns/"), CmisNs.Atom);
            this.AttributeExtensions.Add(new XmlQualifiedName("app", "http://www.w3.org/2000/xmlns/"), CmisNs.App);
            this.AttributeExtensions.Add(new XmlQualifiedName("cmisra", "http://www.w3.org/2000/xmlns/"), CmisNs.Cmisra);
        }

        #endregion

        #region Atom Extensions

        public void AddChildren(TypeContainer[] children)
        {
            // cmisra:children (extension) (dataContractExtension object of type TypeContainer.Children : TypeContainer (serialized as children))
            if (children != null)
            {
                foreach (TypeContainer typeContainer in children)
                {
                    this.ElementExtensions.Add("children", CmisNs.Cmisra, typeContainer.Type); // Alt. typeConteiner.Type.ToXElement()
                }
            }
        }

        /// <summary>
        /// Adds cmisra:content element extension. Takes precedance over atom:content element.
        /// </summary>
        /// <param name="content">The content element to add.</param>
        public void AddContent(Content content)
        {
            AddContent(content, null);
        }

        public void AddContent(Content content, XmlObjectSerializer serializer)
        {
            this.ElementExtensions.Add("content", CmisNs.Cmisra, content, serializer);
        }

        public void AddDocumentId(CopyFlaggedDocumentId documentId)
        {
            // TODO
        }

        /// <summary>
        /// Adds app:edited element extension.
        /// </summary>
        //public void AddEdited()
        //{
        //    // edited (app namespace) TODO: Insert {cmis:lastModiefiedDate}
        //    // The "edited" value must be an RFC-3339 date-time
        //    this.ElementExtensions.Add("edited", CmisNs.App, XmlConvert.ToString(new DateTimeOffset(DateTime.Now)));
        //}

        public void AddDownLink(Uri uri, string mediaType)
        {
            SyndicationLink link = new SyndicationLink()
            {
                Uri = uri,
                RelationshipType = CmisLinkRel.Down,
                MediaType = mediaType,
            };
            this.Links.Add(link);
        }

        /// <summary>
        /// Adds links to current entry instance.
        /// </summary>
        public void AddLinks()
        {
            if (_selfLink != null) this.Links.Add(_selfLink);
            if (_serviceLink != null) this.Links.Add(_serviceLink);
            if (_alternateLink != null) this.Links.Add(_alternateLink);
            if (_currentVersionLink != null) this.Links.Add(_currentVersionLink);
            if (_describedByLink != null) this.Links.Add(_describedByLink);
            if (_downLink != null) this.Links.Add(_downLink);
            if (_editLink != null) this.Links.Add(_editLink);
            if (_editMediaLink != null) this.Links.Add(_editMediaLink);
            if (_enclosureLink != null) this.Links.Add(_enclosureLink);
            if (_upLink != null) this.Links.Add(_upLink);
            if (_versionHistoryLink != null) this.Links.Add(_versionHistoryLink);
            if (_workingCopyLink != null) this.Links.Add(_workingCopyLink);

            if (_aclLink != null) this.Links.Add(_aclLink);
            if (_allowableActionsLink != null) this.Links.Add(_allowableActionsLink);
            if (_folderTreeLink != null) this.Links.Add(_folderTreeLink);
            if (_policiesLink != null) this.Links.Add(_policiesLink);
            if (_relationshipsLink != null) this.Links.Add(_relationshipsLink);
            if (_sourceLink != null) this.Links.Add(_sourceLink);
            if (_targetLink != null) this.Links.Add(_targetLink);
        }

        /// <summary>
        /// Adds cmisra:object element extension. Applied to entries for CMIS Document, Folder, Relationship and Policy objects.
        /// </summary>
        public void AddObject(CmisObject @object)
        {
            this.ElementExtensions.Add(@object.ToXElement());
        }

        /// <summary>
        /// Adds cmisra:pathSegment element extension. Must be used only inside an object parents feed.
        /// </summary>
        /// <param name="relativePathSegment">The pathSegment for the object in that particular folder</param>
        public void AddPathSegment(string relativePathSegment)
        {
            this.ElementExtensions.Add("pathSegment", CmisNs.Cmisra, relativePathSegment);
        }

        /// <summary>
        /// Adds cmisra:relativePathSegment element extension. Must be used only inside an object parents feed.
        /// </summary>
        /// <param name="relativePathSegment">The relative pathSegment for the object in that particular folder</param>
        public void AddRelativePathSegment(string relativePathSegment)
        {
            this.ElementExtensions.Add("relativePathSegment", CmisNs.Cmisra, relativePathSegment);
        }

        /// <summary>
        /// Adds cmis:properties element extension.
        /// </summary>
        /// <param name="properties">The property object to add.</param>
        public void AddProperties(CmisProperties properties)
        {
            this.ElementExtensions.Add("properties", CmisNs.Cmis, properties);
        }

        /// <summary>
        /// Adds cmisra:type element extension. Applied to entries for CMIS Type Definitions.
        /// </summary>
        /// <param name="type">The type definition that this atom entry represents.</param>
        public void AddType(TypeDefinition @type)
        {
            this.ElementExtensions.Add(@type.ToXElement());
        }

        #endregion

        #region Factory Methods

        /// <summary>
        /// Gets an atom entry, with the specified type definition enclosed.
        /// </summary>
        /// <param name="repositoryId">The ID of the repository.</param>
        /// <param name="typeDefinition">The type defintion to enclose.</param>
        /// <returns>Atom entry, with enclosed type definition.</returns>
        public static CmisSyndicationItem CreateEntry(string repositoryId, TypeDefinition typeDefinition)
        {
            if (typeDefinition == null)
            {
                return new CmisSyndicationItem();
            }

            // NOTE: Cannot use colon sign, ":", in url. Generates 400 response from ISS, See AllowRestrictedChars.
            string cmisTypeId = typeDefinition.Id.Replace(':', '_');

            CmisSyndicationItem entry = new CmisSyndicationItem()
            {
                Title = new TextSyndicationContent(typeDefinition.DisplayName),
                Content = new TextSyndicationContent(String.Format("Type Definition - {0}", typeDefinition.QueryName)), // Content ?
                Id = String.Format("urn:uuid:{0}-type", typeDefinition.QueryName),
                LastUpdatedTime = new DateTimeOffset(DateTime.Now),
                PublishDate = new DateTimeOffset(DateTime.Now),
                Summary = new TextSyndicationContent(String.Format("Represents a {0} type.", typeDefinition.DisplayName)),
                SelfLinkUri = new Uri(CmisRestAtom.ServiceUri, String.Format("{0}/type/{1}", repositoryId, cmisTypeId)),
                ServiceLinkUri = new Uri(CmisRestAtom.ServiceUri, String.Format("{0}/servicedoc", repositoryId)),
                DescribedByLinkUri = new Uri(CmisRestAtom.ServiceUri, String.Format("{0}/type/{1}", repositoryId, cmisTypeId)),
                DownLinkUri = new Uri(CmisRestAtom.ServiceUri, String.Format("{0}/types?typeId={1}", repositoryId, cmisTypeId)),
            };

            entry.AddDownLink(new Uri(CmisRestAtom.ServiceUri, String.Format("{0}/typedescendants/{1}", repositoryId, cmisTypeId)), CmisContentType.Tree);
            if (typeDefinition.ParentId != null)
            {
                entry.UpLinkUri = new Uri(CmisRestAtom.ServiceBaseUri, String.Format("{0}/type/{1}", repositoryId, typeDefinition.ParentId.Replace(':','_')));
            }
            entry.AddLinks();

            // atom:author/atom:name MUST be cmis:createdBy
            entry.Authors.Add(CmisRestAtom.Author);

            // app:edited MUST be cmis:lastModifiedDate. The value must be an RFC-3339 date-time
            entry.ElementExtensions.Add("edited", CmisNs.App, XmlConvert.ToString(new DateTimeOffset(DateTime.Now)));

            entry.AddType(typeDefinition);

            return entry;
        }

        /// <summary>
        /// Creates CMIS object entry.
        /// </summary>
        /// <param name="repositoryId">The current repository ID.</param>
        /// <param name="cmisObject">The CMIS object to create an entry from.</param>
        /// <returns>A <see cref="NCMIS.ServiceModel.Syndication.CmisSyndicationItem"/> instance representing an Atom entry.</returns>
        public static CmisSyndicationItem CreateEntry(string repositoryId, CmisObject cmisObject)
        {
            if (cmisObject == null)
            {
                return new CmisSyndicationItem();
            }

            string cmisCreatedBy = cmisObject.Properties.CreatedBy.SingleValue;
            DateTime cmisCreatedDate = cmisObject.Properties.CreationDate.SingleValue;
            DateTime cmisLastModifiedDate = cmisObject.Properties.LastModificationDate.SingleValue;
            string cmisName = cmisObject.Properties.Name.SingleValue;
            string cmisObjectId = cmisObject.Properties.ObjectId.SingleValue;

            CmisPropertyId baseType = cmisObject.Properties.BaseTypeId;
            if (baseType == null) baseType = new CmisPropertyId() { Value = new string[] { "cmis:default" } };

            CmisSyndicationItem entry = new CmisSyndicationItem()
            {
                // atom:title MUST be the cmis:name property
                Title = new TextSyndicationContent(cmisName),
                // atom:id SHOULD be derived from cmis:objectId
                Id = String.Format("urn:uuid:{0}-object", cmisObjectId),
                // atom:updated MUST be cmis:lastModifiedDate 
                LastUpdatedTime = new DateTimeOffset(cmisLastModifiedDate),
                // atom:published MUST be cmis:createdDate 
                PublishDate = new DateTimeOffset(cmisCreatedDate),
                AllowableActionsLinkUri = new Uri(CmisRestAtom.ServiceUri, String.Format("{0}/object/{1}/allowableactions", repositoryId, cmisObject.Id)),
                DescribedByLinkUri = new Uri(CmisRestAtom.ServiceUri, String.Format("{0}/type/{1}", repositoryId, cmisObject.Properties.BaseTypeId.SingleValue.Replace(':', '_'))),
                DownLinkUri = new Uri(CmisRestAtom.ServiceUri, String.Format("{0}/children/{1}", repositoryId, cmisObject.Id)),
                SelfLinkUri = new Uri(CmisRestAtom.ServiceUri, String.Format("{0}/object/{1}", repositoryId, cmisObject.Id)),
                ServiceLinkUri = new Uri(CmisRestAtom.ServiceUri, String.Format("{0}/servicedoc", repositoryId)),
            };

            // atom:author/atom:name MUST be cmis:createdBy
            entry.Authors.Add(new SyndicationPerson() { Name = cmisCreatedBy });

            // app:edited MUST be cmis:lastModifiedDate. The value must be an RFC-3339 date-time
            entry.ElementExtensions.Add("edited", CmisNs.App, XmlConvert.ToString(new DateTimeOffset(cmisLastModifiedDate)));

            // TODO: if (object.isSecurable)
            // {
            //     // Acl URI must mathc the URIs of service methods: getAcl (GET)
            //     entry.AclLinkUri = new Uri(_baseUri, String.Format("{0}/object/{1}/acl", repositoryId, objectId));
            // }

            // Links specific to cmis:document, cmis:folder, cmis:policy
            if (((IList<string>)baseType.Value).Contains("cmis:document") || ((IList<string>)baseType.Value).Contains("cmis:folder") || ((IList<string>)baseType.Value).Contains("cmis:policy"))
            {
                entry.RelationshipsLinkUri = new Uri(CmisRestAtom.ServiceUri, String.Format("{0}/object/{1}/relationships", repositoryId, cmisObject.Id));
            }

            // Links specific to cmis:document, cmis:folder
            if (((IList<string>)baseType.Value).Contains("cmis:document") || ((IList<string>)baseType.Value).Contains("cmis:folder"))
            {
                if (cmisObject.Properties.ParentId != null)
                {
                    entry.UpLinkUri = new Uri(CmisRestAtom.ServiceUri, String.Format("{0}/object/{1}", repositoryId, cmisObject.Properties.ParentId.SingleValue));
                }

                // Policies URI must match URIs of service methods: getAppliedPolicies (GET)
                entry.PoliciesLinkUri = new Uri(CmisRestAtom.ServiceUri, String.Format("{0}/object/{1}/policies", repositoryId, cmisObject.Id));
            }

            // Links specific to cmis:document
            if (((IList<string>)baseType.Value).Contains("cmis:document"))
            {
                // TODO: Implement alternate link matching the service method getContentStream (GET) for expressing renditions as CMIS resources
                //entry.AlternateLinkUri = new Uri(_baseUri, String.Format("{0}/object/{1}/rendition", repositoryId, objectId));
                // Current Version URI must match URIs of service methods: getObjectOfLatestVersion (GET)
                entry.CurrentVersionLinkUri = new Uri(CmisRestAtom.ServiceUri, String.Format("{0}/object/{1}/currentversion", repositoryId, cmisObject.Id));
                // Edit URI must match URIs of service methods: getObject (GET), updateProperties (PUT) 
                entry.EditLinkUri = new Uri(CmisRestAtom.ServiceUri, String.Format("{0}/object/{1}", repositoryId, cmisObject.Id));
                // Edit-Media URI must match URIs of service methods: SetContentStream (PUT), DeleteContentStream (DELETE) 
                entry.EditMediaLinkUri = new Uri(CmisRestAtom.ServiceUri, String.Format("{0}/object/{1}/content", repositoryId, cmisObject.Id));
                // Microformat indicating that the destination of the link is intended to be downloaded (Not specified by CMIS)
                entry.EnclosureLinkUri = new Uri(CmisRestAtom.ServiceUri, String.Format("{0}/object/{1}/content", repositoryId, cmisObject.Id));
                // Version History URI must match URIs of service methods: getAllVersions (GET)
                entry.VersionHistoryLinkUri = new Uri(CmisRestAtom.ServiceUri, String.Format("{0}/object/{1}/allversions", repositoryId, cmisObject.Id));
                entry.Content = new UrlSyndicationContent(new Uri(CmisRestAtom.ServiceUri, String.Format("{0}/object/{1}/content", repositoryId, cmisObject.Id)), CmisContentType.Stream);
                // TODO: Implement PWC, service getObject (GET) for private-working-copy specified by cmis:versionSeriesCheckedOutId property
                //entry.WorkingCopyLinkUri = new Uri(_baseUri, String.Format("{0}/object/{1}", repositoryId, objectId));
            }

            // Links specific to cmis:relationship
            if (((IList<string>)baseType.Value).Contains("cmis:relationship"))
            {
                // TODO: When used on a CMIS Relationship resource, this link relation MUST point to an atom entry document for the CMIS Resource
                //       specified by the cmis:sourceId property on the relationship. 
                //entry.SourceLinkUri = new Uri(_baseUri, String.Format("{0}/object/{1}", repositoryId, objectId));
                // TODO: When used on a CMIS Relationship resource, this link relation MUST point to an atom entry document for the CMIS Resource
                //       specified by the cmis:targetId property on the relationship. 
                //entry.TargetLinkUri = new Uri(_baseUri, String.Format("{0}/object/{1}", repositoryId, objectId));
            }

            entry.AddLinks();

            entry.AddObject(cmisObject);

            return entry;
        }

        #endregion

        #region Content Methods

        /// <summary>
        /// Creates a <see cref="NCMIS.ObjectModel.Rest.Content"/> object.
        /// </summary>
        /// <param name="data">Thebyte content for the new object.</param>
        /// <param name="fileName">The filename that will descide the MimeType of the new object.</param>
        /// <returns>A <see cref="NCMIS.ObjectModel.Rest.Content"/> object.</returns>
        public static Content CreateCmisraContent(byte[] data, string fileName)
        {
            return new Content()
            {
                MediaType = GetMimeType(fileName),
                Base64 = Convert.ToBase64String(data)
            };
        }

        public static CmisRaContentStream CreateCmisraContentStream(string fileName, Stream contentStream)
        {
            return new CmisRaContentStream(CmisSyndicationItem.CreateCmisraContent(new Byte[0], fileName), contentStream);
        }

        /// <summary>
        /// Gets the MimeType from a file name.
        /// </summary>
        /// <param name="fileName">The file name to get MimeType of.</param>
        /// <returns>A <see cref="System.String"/> MimeType.</returns>
        private static string GetMimeType(string fileName)
        {
            // Code from http://kseesharp.blogspot.com/2008/04/c-get-mimetype-from-file-name.html

            string mimeType = "application/unknown";
            string extension = System.IO.Path.GetExtension(fileName).ToLower();

            Microsoft.Win32.RegistryKey regKey = Microsoft.Win32.Registry.ClassesRoot.OpenSubKey(extension);
            if (regKey != null && regKey.GetValue("Content Type") != null)
            {
                mimeType = regKey.GetValue("Content Type").ToString();
            }
            return mimeType;
        }

        /// <summary>
        /// Retrieves the content stream from a SyndicationItem.
        /// </summary>
        /// <param name="syndicationItem">The SyndicationItem to get the stream from.</param>
        /// <returns>A <see cref="NCMIS.ObjectModel.ContentStream"/> object, or null if no content was found.</returns>
        public static ContentStream GetContentStream(SyndicationItem syndicationItem)
        {
            // cmisra:content takes precedence over atom:content
            ContentStream contentStream = null;
            if ((contentStream = GetRaContent(syndicationItem)) != null)
            {
                return contentStream;
            }
            return GetAtomContent(syndicationItem);
        }

        /// <summary>
        /// Retrieves syndication content.
        /// </summary>
        /// <param name="syndicationItem">The SyndicationItem to get the stream from.</param>
        /// <returns>A <see cref="NCMIS.ObjectModel.ContentStream"/> object, or null if no content was found.</returns>
        private static ContentStream GetAtomContent(SyndicationItem syndicationItem)
        {
            byte[] data = null;
            if (syndicationItem.Content.GetType() == typeof(TextSyndicationContent))
            {
                string textContent = (syndicationItem.Content as TextSyndicationContent).Text;
                data = System.Text.ASCIIEncoding.UTF8.GetBytes(textContent);
            }
            else if (syndicationItem.Content.GetType() == typeof(XmlSyndicationContent))
            {
                XmlDictionaryReader reader = (syndicationItem.Content as XmlSyndicationContent).GetReaderAtContent();
                string val =  (XElement.ReadFrom(reader) as XElement).Value;
                data = Convert.FromBase64String(val);
            }
            else if (syndicationItem.Content.GetType() == typeof(UrlSyndicationContent))
            {
                // Not handled
                return null;
            }
            else
            {
                // atom:content was not found
                return null;
            }

            return new ContentStream()
            {
                Filename = syndicationItem.Title.Text,
                Length = (uint)data.Length,
                MimeType = syndicationItem.Content.Type,
                Stream = data
            };
        }

        // 
        /// <summary>
        /// Retrieves cmisra:content.
        /// </summary>
        /// <param name="syndicationItem">The SyndicationItem to get the stream from.</param>
        /// <returns>A <see cref="NCMIS.ObjectModel.ContentStream"/> object, or null if no content was found.</returns>
        private static ContentStream GetRaContent(SyndicationItem syndicationItem)
        {
            ContentStream contentStream = null;
            foreach (SyndicationElementExtension extension in syndicationItem.ElementExtensions.Where<SyndicationElementExtension>(x => x.OuterName == "content" && x.OuterNamespace == CmisNs.Cmisra))
            {
                contentStream = new ContentStream();
                contentStream.Filename = syndicationItem.Title.Text;
                XElement xelement = XElement.ReadFrom(extension.GetReader()) as XElement;
                foreach (XNode node in xelement.Nodes())
                {
                    if (node.NodeType == XmlNodeType.Element)
                    {
                        XElement currentElement = node as XElement;
                        switch (currentElement.Name.LocalName)
                        {
                            case "mediatype":
                                contentStream.MimeType = currentElement.Value;
                                break;
                            case "base64":
                                byte[] data = Convert.FromBase64String(currentElement.Value);
                                contentStream.Stream = data;
                                contentStream.Length = (uint?)data.Length;
                                break;
                        }
                    }

                }
            }
            return contentStream;
        }

        #endregion

        public static SyndicationLink GetLink(SyndicationItem item, string linkRel)
        {
            SyndicationLink result = null;
            if (item != null)
            {
                foreach (SyndicationLink link in item.Links.Where<SyndicationLink>(x => x.RelationshipType == linkRel))
                {
                    result = link;
                    break;
                }
            }
            return result;
        }

        /// <summary>
        /// Gets the cmisra:object element from an Atom entry. If more than one cmisra:object elements exists, only the first is retrieved.
        /// </summary>
        /// <param name="item">The entry from which to retreive the cmisra:object element.</param>
        /// <returns>The cmisra:object element formatted as a <see cref="NCMIS.ObjectModel.CmisObject"/>. If no cmisra:object is found, null is returned.</returns>
        public static CmisObject GetObject(SyndicationItem item)
        {
            CmisObject cmisObject = null;
            CmisProperties cmisProperties = null;
            XNamespace ns = CmisNs.Cmis;

            // Prints the item (SyndicationItem) in XML format to file
            //System.Xml.XmlWriter atomWriter = System.Xml.XmlWriter.Create(@"C:\EPiServer\Sites\DefaultSite\atom.xml");
            //Atom10ItemFormatter atomFormatter = new Atom10ItemFormatter(item);
            //atomFormatter.WriteTo(atomWriter);
            //atomWriter.Close();

            // TO DO: Find a way to get the ACL
            // The code below is a prototype that fetches the ACL in XML form from the link provided by each SyndicationItem.
            //foreach (SyndicationLink sl in item.Links)
            //{
            //    if (sl.GetAbsoluteUri().ToString().EndsWith("/acl"))
            //    {
                    // Option 1: Read XML as temporarily stored string
                    //byte[] ba = _cmisWebReq.GetContentStreamAsByteArray(sl.GetAbsoluteUri());
                    //string xmlString = System.Text.ASCIIEncoding.ASCII.GetString(ba);
                    // XmlTextReader reader = new XmlTextReader(new System.IO.StringReader(xmlString));
                    // Read and parse ACL XML response from repository.
                    // ...

                    // Option 2: Read XML directly from stream
                    //Stream acls = _cmisWebReq.GetContentStream(sl.GetAbsoluteUri());
                    //XmlTextReader reader = new XmlTextReader(acls);
                    // Read and parse ACL XML response from repository.
                    // ...
            //    }
            //}

            // Process each atom entry and extract its properties (only process the <cmisra:object> node and its children)
            foreach (SyndicationElementExtension extension in item.ElementExtensions.Where<SyndicationElementExtension>(x => x.OuterName == "object"))
            {
                // Create an XElement for the CMIS object
                XElement objectElement = new XElement(ns + "object");
                objectElement.SetAttributeValue("xmlns", ns.NamespaceName);
                objectElement.Name = ns + "object";

                XElement objectPropertiesElement = new XElement(ns + "CmisProperties");     // Not ussed?
                objectPropertiesElement.SetAttributeValue("xmlns", ns.NamespaceName);
                objectPropertiesElement.Name = ns + "CmisProperties";

                XElement objectPropertiesElement2 = new XElement(ns + "properties");
                objectPropertiesElement2.SetAttributeValue("xmlns", ns.NamespaceName);
                objectPropertiesElement2.Name = ns + "properties";

                XNode xnode = XElement.ReadFrom(extension.GetReader());
                XElement xelement = (XElement)xnode;

                if (xelement.Name.NamespaceName.Equals(CmisNs.Cmisra))
                {
                    foreach (XNode node in xelement.Nodes())
                    {
                        if (node.NodeType == XmlNodeType.Element)
                        {
                            XElement tempElement = (XElement)node;
                            XName tempName = tempElement.Name;

                            if (tempElement.Name.NamespaceName.Equals(CmisNs.Cmis))
                            {
                                //objectElement.Add(node);

                                List<CmisProperty> propList = new List<CmisProperty>();

                                // When capabilites are read direct from the RepositoryInfo object the 
                                // DataContractSerializer.ReadObject method throws SerializationException:
                                // "Expecting state 'Element'.. Encountered 'Text' with name '', namespace ''."
                                // (Only for Alfresco's service document). Most probably the service document
                                // contains some space characters (due to indenting and making the XML human readable)
                                // that are not allowed by WCF DataContractSerializer.
                                if (!tempElement.Name.LocalName.Equals("properties"))
                                {
                                    objectElement.Add(node); // NEW
                                }
                                else
                                {
                                    XElement subEl = (XElement)node;
                                    foreach (XNode subNode in subEl.Nodes())
                                    {
                                        if (subNode.NodeType.Equals(XmlNodeType.Element))
                                        {
                                            XElement propEl = (XElement)subNode;
                                            objectPropertiesElement2.Add(subNode);

                                            // Get element properties
                                            CmisProperty prop = null;
                                            prop = GetProperty(propEl);
                                            if (prop != null) propList.Add(prop);
                                        }
                                    }

                                    //objectPropertiesElement.Add(node); // NEW
                                }
                                // Save the element properties in a CmisProperties object.
                                cmisProperties = new CmisProperties() { Items = propList.ToArray() };
                            }
                        }
                    }
                    objectPropertiesElement.Add(objectPropertiesElement2);
                }

                //objectElement.Save("ncmis_object.xml");

                cmisObject = ReadObject<CmisObject>(objectElement.CreateReader());
                CmisProperties properties = ReadObject<CmisProperties>(objectPropertiesElement.CreateReader());

                cmisObject.Properties = cmisProperties;
            }

            return cmisObject;
        }

        #region Helpers

        private static T ReadObject<T>(XmlReader reader)
        {
            T type;
            DataContractSerializer ser = new DataContractSerializer(typeof(T));
            try
            {
                type = (T)ser.ReadObject(reader, true);
                reader.Close();
            }
            catch (Exception ex)
            {
                Console.Write(ex.Message);
                return default(T);
            }
            return type;
        }

        /// <summary>
        /// Get CMIS properties from the Atom feed
        /// </summary>
        /// <param name="propertyElement"></param>
        /// <returns></returns>
        private static CmisProperty GetProperty(XElement propertyElement)
        {
            // CMIS property types
            CmisPropertyBoolean propertyBoolean = null;
            CmisPropertyDateTime propertyDateTime = null;
            CmisPropertyDecimal propertyDecimal = null;
            CmisPropertyHtml propertyHtml = null;
            CmisPropertyId propertyId = null;
            CmisPropertyInteger propertyInteger = null;
            CmisPropertyString propertyString = null;
            CmisPropertyUri propertyUri = null;
            
            // Set the value of the property depending on the property type.
            switch (propertyElement.Name.LocalName)
            {
                case ("propertyBoolean"):
                    propertyBoolean = new CmisPropertyBoolean();
                    propertyBoolean.DisplayName = GetPropertyAttribute(propertyElement, "displayName");
                    propertyBoolean.LocalName = GetPropertyAttribute(propertyElement, "localName");
                    propertyBoolean.PropertyDefinitionId = GetPropertyAttribute(propertyElement, "propertyDefinitionId");
                    propertyBoolean.QueryName = GetPropertyAttribute(propertyElement, "queryName");
                    if (propertyElement.Value != null)
                    {
                        //System.Diagnostics.Debug.WriteLine(propertyElement.ToString());
                        try
                        {
                            propertyBoolean.Value = new bool[] { bool.Parse(propertyElement.Value.Trim()) };
                        }
                        catch (FormatException fe)
                        {
                            System.Diagnostics.Debug.WriteLine(fe.Message);
                            // One of the propertyElements has a multi-valued boolean which
                            // throws an exception; how should this be handled? 
                        }                        
                    }
                    return propertyBoolean;

                case ("propertyDateTime"):
                    propertyDateTime = new CmisPropertyDateTime();
                    // Set various attributes
                    propertyDateTime.DisplayName = GetPropertyAttribute(propertyElement, "displayName");
                    propertyDateTime.LocalName = GetPropertyAttribute(propertyElement, "localName");
                    propertyDateTime.PropertyDefinitionId = GetPropertyAttribute(propertyElement, "propertyDefinitionId");
                    propertyDateTime.QueryName = GetPropertyAttribute(propertyElement, "queryName");
                    if (propertyElement.Value != null)
                    {
                        //System.Diagnostics.Debug.WriteLine(propertyElement.ToString());
                        try
                        {
                            propertyDateTime.Value = new DateTime[] { DateTime.Parse(propertyElement.Value.Trim()) };
                        }
                        catch (FormatException fe)
                        {
                            System.Diagnostics.Debug.WriteLine(fe.Message);
                        }
                    }
                    return propertyDateTime;

                case ("propertyDecimal"):
                    propertyDecimal = new CmisPropertyDecimal();
                    // Set various attributes
                    propertyDecimal.DisplayName = GetPropertyAttribute(propertyElement, "displayName");
                    propertyDecimal.LocalName = GetPropertyAttribute(propertyElement, "localName");
                    propertyDecimal.PropertyDefinitionId = GetPropertyAttribute(propertyElement, "propertyDefinitionId");
                    propertyDecimal.QueryName = GetPropertyAttribute(propertyElement, "queryName");
                    if (propertyElement.Value != null)
                    {
                        propertyDecimal.Value = new Decimal[] { Decimal.Parse(propertyElement.Value.Trim()) };
                    }
                    return propertyDecimal;

                case ("propertyHtml"):
                    propertyHtml = new CmisPropertyHtml();
                    propertyHtml.DisplayName = GetPropertyAttribute(propertyElement, "displayName");
                    propertyHtml.LocalName = GetPropertyAttribute(propertyElement, "localName");
                    propertyHtml.PropertyDefinitionId = GetPropertyAttribute(propertyElement, "propertyDefinitionId");
                    propertyHtml.QueryName = GetPropertyAttribute(propertyElement, "queryName");
                    if (propertyElement.Value != null)
                    {
                        propertyHtml.Value = new string[] { propertyElement.Value.Trim() };
                    }
                    return propertyHtml;

                case ("propertyId"):
                    propertyId = new CmisPropertyId();
                    propertyId.DisplayName = GetPropertyAttribute(propertyElement, "displayName");
                    propertyId.LocalName = GetPropertyAttribute(propertyElement, "localName");
                    propertyId.PropertyDefinitionId = GetPropertyAttribute(propertyElement, "propertyDefinitionId");
                    propertyId.QueryName = GetPropertyAttribute(propertyElement, "queryName");
                    if (propertyElement.Value != null)
                    {
                        propertyId.Value = new string[] { propertyElement.Value.Trim() };
                    }
                    return propertyId;

                case ("propertyInteger"):
                    propertyInteger = new CmisPropertyInteger();
                    propertyInteger.DisplayName = GetPropertyAttribute(propertyElement, "displayName");
                    propertyInteger.LocalName = GetPropertyAttribute(propertyElement, "localName");
                    propertyInteger.PropertyDefinitionId = GetPropertyAttribute(propertyElement, "propertyDefinitionId");
                    propertyInteger.QueryName = GetPropertyAttribute(propertyElement, "queryName");
                    if (propertyElement.Value != null)
                    {
                        //System.Diagnostics.Debug.WriteLine(propertyElement.ToString());     // Debug
                        try
                        {
                            propertyInteger.Value = new int[] { int.Parse(propertyElement.Value.Trim()) };
                        }
                        catch (FormatException fe)
                        {
                            System.Diagnostics.Debug.WriteLine(fe.Message); // (Input string was not in a correct format).
                            // The propertyElement doesn't have a <cmis:value> element.
                            // <cmis:propertyInteger propertyDefinitionId="mycm:anumber" 
                            //                       displayName="A number" 
                            //                       queryName="mycm:anumber" 
                            //                       xmlns:cmis="http://docs.oasis-open.org/ns/cmis/core/200908/">
                            //</cmis:propertyInteger>
                        }
                    }
                    return propertyInteger;

                case ("propertyString"):
                    propertyString = new CmisPropertyString();
                    propertyString.DisplayName = GetPropertyAttribute(propertyElement, "displayName");
                    propertyString.LocalName = GetPropertyAttribute(propertyElement, "localName");
                    propertyString.PropertyDefinitionId = GetPropertyAttribute(propertyElement, "propertyDefinitionId");
                    propertyString.QueryName = GetPropertyAttribute(propertyElement, "queryName");
                    if (propertyElement.Value != null)
                    {
                        propertyString.Value = new string[] { propertyElement.Value.Trim() };
                    }
                    return propertyString;

                case ("propertyUri"):
                    propertyUri = new CmisPropertyUri();
                    propertyUri.DisplayName = GetPropertyAttribute(propertyElement, "displayName");
                    propertyUri.LocalName = GetPropertyAttribute(propertyElement, "localName");
                    propertyUri.PropertyDefinitionId = GetPropertyAttribute(propertyElement, "propertyDefinitionId");
                    propertyUri.QueryName = GetPropertyAttribute(propertyElement, "queryName");
                    if (propertyElement.Value != null)
                    {
                        propertyUri.Value = new string[] { propertyElement.Value.Trim() };
                    }
                    return propertyUri;
            }
            return null;
        }

        /// <summary>
        /// Gets a property attribute value from a CMIS property <see cref="System.xml.Linq.XElement"/>. If no attribute was found, null is returned.
        /// </summary>
        /// <param name="propertyElement">The CMIS property <see cref="System.xml.Linq.XElement"/> to retreive the value attribute value.</param>
        /// <param name="name">The name of the attribute.</param>
        /// <returns>The attribute value as a <see cref="System.String"/>, or null if the attribute was not found.</returns>
        private static string GetPropertyAttribute(XElement propertyElement, string name)
        {
            string attribute = null;
            if (propertyElement.Attribute(name) != null)
            {
                attribute = propertyElement.Attribute(name).Value;
            }
            return attribute;
        }

        #endregion
    }
}
